#ifndef BIN_OPS_OUT_CLASS_H
#define BIN_OPS_OUT_CLASS_H

template <class T1, class T2, class OP>
struct bin_ops_deducer
{
	struct sub_fail {};

	typedef typename as_expression<T1>::expr_T expr_T1;
	typedef typename as_expression<T2>::expr_T expr_T2;
	typedef expression_binary<expr_T1, expr_T2, OP> expr_result_T;
		
	inline static expression<expr_result_T> dispatch( const T1& v1, const T2& v2 )
	{		
		return expression<expr_result_T>( 
			expr_result_T(as_expression<T1>::create(v1), as_expression<T2>::create(v2) )
		);
	}

	static sub_fail	dispatch( ... ); //! SFINAE
};

#define DECLARE_ARRAY_EXPR_BIN_OPS_OUT_CLASS( OP, OP_CLASS ) 										\
																									\
/* T op array<T,N> */																				\
template <typename left_T, typename right_T, unsigned int DIM>										\
auto operator OP( const left_T& v1, const array<right_T, DIM>& v2 ) ->								\
decltype( bin_ops_deducer<left_T, array<right_T, DIM>, OP_CLASS>::dispatch(v1, v2) )				\
{  return bin_ops_deducer<left_T, array<right_T, DIM>, OP_CLASS>::dispatch(v1, v2); }				\
																									\
/* array<T,DIM> op T */																				\
template <typename left_T, typename right_T, unsigned int DIM>										\
auto operator OP( const array<left_T, DIM>& v1,const right_T& v2 ) ->								\
decltype( bin_ops_deducer<array<left_T, DIM>, right_T, OP_CLASS>::dispatch(v1, v2) )				\
{  return bin_ops_deducer<array<left_T, DIM>, right_T, OP_CLASS>::dispatch(v1, v2); }				\
																									\
/* array<T,DIM> op array<T,DIM> */																	\
template <typename left_T, typename right_T, unsigned int DIM>										\
auto operator OP( const array<left_T, DIM>& v1,const array<right_T, DIM>& v2 ) ->					\
decltype( bin_ops_deducer<array<left_T, DIM>, array<right_T, DIM>, OP_CLASS>::dispatch(v1, v2) )	\
{  return bin_ops_deducer<array<left_T, DIM>, array<right_T, DIM>, OP_CLASS>::dispatch(v1, v2); }	\
																									\
/* array<T,DIM> op expression<Expr_T> */															\
template <typename left_T, typename right_T, unsigned int DIM>										\
auto operator OP( const array<left_T, DIM>& v1,const expression<right_T>& v2 ) ->					\
decltype( bin_ops_deducer<array<left_T, DIM>, expression<right_T>, OP_CLASS>::dispatch(v1, v2) )	\
{  return bin_ops_deducer<array<left_T, DIM>, expression<right_T>, OP_CLASS>::dispatch(v1, v2); }	\
																									\
/* expression<Expr_T> op array<T,DIM> */															\
template <typename left_T, typename right_T, unsigned int DIM>										\
auto operator OP( const expression<left_T>& v1,const array<right_T, DIM>& v2 ) ->					\
decltype( bin_ops_deducer<expression<left_T>, array<right_T, DIM>, OP_CLASS>::dispatch(v1, v2) )	\
{  return bin_ops_deducer<expression<left_T>, array<right_T, DIM>, OP_CLASS>::dispatch(v1, v2); }	\
																									\
/* expression<Expr_T> op T */																		\
template <typename left_T, typename right_T>														\
auto operator OP( const expression<left_T>& v1,const right_T& v2 ) ->								\
decltype( bin_ops_deducer<expression<left_T>, right_T, OP_CLASS>::dispatch(v1, v2) )				\
{  return bin_ops_deducer<expression<left_T>, right_T, OP_CLASS>::dispatch(v1, v2); }				\
																									\
/* T op expression<Expr_T> */																		\
template <typename left_T, typename right_T>														\
auto operator OP( const left_T& v1,const expression<right_T>& v2 ) ->								\
decltype( bin_ops_deducer<left_T, expression<right_T>, OP_CLASS>::dispatch(v1, v2) )				\
{  return bin_ops_deducer<left_T, expression<right_T>, OP_CLASS>::dispatch(v1, v2); }				\
																									\
/* expression<Expr_T> op expression<Expr_T> */														\
template <typename left_T, typename right_T>														\
auto operator OP( const expression<left_T>& v1,const expression<right_T>& v2 ) ->					\
decltype( bin_ops_deducer<expression<left_T>, expression<right_T>, OP_CLASS>::dispatch(v1, v2) )	\
{  return bin_ops_deducer<expression<left_T>, expression<right_T>, OP_CLASS>::dispatch(v1, v2); }
	
DECLARE_ARRAY_EXPR_BIN_OPS_OUT_CLASS( +, binary_ops::plus_op );
DECLARE_ARRAY_EXPR_BIN_OPS_OUT_CLASS( -, binary_ops::minus_op );
DECLARE_ARRAY_EXPR_BIN_OPS_OUT_CLASS( *, binary_ops::multiply_op );
DECLARE_ARRAY_EXPR_BIN_OPS_OUT_CLASS( /, binary_ops::divide_op );
DECLARE_ARRAY_EXPR_BIN_OPS_OUT_CLASS( %, binary_ops::mod_op );
DECLARE_ARRAY_EXPR_BIN_OPS_OUT_CLASS( ^, binary_ops::xor_op );
DECLARE_ARRAY_EXPR_BIN_OPS_OUT_CLASS( &, binary_ops::bitand_op );
DECLARE_ARRAY_EXPR_BIN_OPS_OUT_CLASS( |, binary_ops::bitor_op );
DECLARE_ARRAY_EXPR_BIN_OPS_OUT_CLASS( >>, binary_ops::right_shift_op );
DECLARE_ARRAY_EXPR_BIN_OPS_OUT_CLASS( <<, binary_ops::left_shift_op );

DECLARE_ARRAY_EXPR_BIN_OPS_OUT_CLASS( >, binary_ops::greater_op )
DECLARE_ARRAY_EXPR_BIN_OPS_OUT_CLASS( <, binary_ops::less_op )
DECLARE_ARRAY_EXPR_BIN_OPS_OUT_CLASS( >=, binary_ops::greater_or_equal_op )
DECLARE_ARRAY_EXPR_BIN_OPS_OUT_CLASS( <=, binary_ops::less_or_equal_op )
DECLARE_ARRAY_EXPR_BIN_OPS_OUT_CLASS( ==, binary_ops::equal_op )
DECLARE_ARRAY_EXPR_BIN_OPS_OUT_CLASS( !=, binary_ops::not_equal_op )
DECLARE_ARRAY_EXPR_BIN_OPS_OUT_CLASS( &&, binary_ops::logical_and_op )
DECLARE_ARRAY_EXPR_BIN_OPS_OUT_CLASS( ||, binary_ops::logical_or_op )

//////////////////////////////////////////////////////
//	for tiny_vector
//	Instead of generating expressions, 
//	here we evaluate directly
//////////////////////////////////////////////////////

#define DECLARE_TINYVEC_BIN_OPS_OUT_CLASS( OP, BIN_ASN_OP )							\
																					\
template <typename T1, typename T2, unsigned int N>									\
tiny_vector< decltype( std::declval<T1>() OP std::declval<T2>() ), N >				\
operator OP( const tiny_vector<T1, N>& v1, const tiny_vector<T2, N>& v2 )			\
{																					\
	tiny_vector< decltype( std::declval<T1>() OP std::declval<T2>() ), N > res = v1;\
	res BIN_ASN_OP v2;																\
	return std::move( res );														\
}																					\
																					\
template <typename T1, typename T2, unsigned int N>									\
tiny_vector< decltype( std::declval<T1>() OP std::declval<T2>() ), N >				\
operator OP( const tiny_vector<T1, N>& v1, const T2&& v2 )							\
{																					\
	tiny_vector< decltype( std::declval<T1>() OP std::declval<T2>() ), N > res = v1;\
	res BIN_ASN_OP v2;																\
	return std::move( res );														\
}																					\
																					\
template <typename T1, typename T2, unsigned int N>									\
tiny_vector< decltype( std::declval<T1>() OP std::declval<T2>() ), N >				\
operator OP( const T1&& v1, const tiny_vector<T2, N>& v2 )							\
{																					\
	tiny_vector< decltype( std::declval<T1>() OP std::declval<T2>() ), N > res = v2;\
	res BIN_ASN_OP v1;																\
	return std::move( res );														\
}																					

DECLARE_TINYVEC_BIN_OPS_OUT_CLASS( +, += )
DECLARE_TINYVEC_BIN_OPS_OUT_CLASS( -, -= )
DECLARE_TINYVEC_BIN_OPS_OUT_CLASS( *, *= )
DECLARE_TINYVEC_BIN_OPS_OUT_CLASS( /, /= )
DECLARE_TINYVEC_BIN_OPS_OUT_CLASS( %, %= )
DECLARE_TINYVEC_BIN_OPS_OUT_CLASS( ^, ^= )
DECLARE_TINYVEC_BIN_OPS_OUT_CLASS( &, &= )
DECLARE_TINYVEC_BIN_OPS_OUT_CLASS( |, |= )
DECLARE_TINYVEC_BIN_OPS_OUT_CLASS( >>, >>= )
DECLARE_TINYVEC_BIN_OPS_OUT_CLASS( <<, <<= )


#endif //!< BIN_OPS_OUT_CLASS_H